package com.tarena.lbs.article.web.service;

import com.alibaba.nacos.common.utils.CollectionUtils;
import com.tarena.lbs.article.web.repository.ArticleESRepository;
import com.tarena.lbs.article.web.repository.ArticleRepository;
import com.tarena.lbs.attach.api.AttachApi;
import com.tarena.lbs.base.common.utils.Asserts;
import com.tarena.lbs.base.protocol.exception.BusinessException;
import com.tarena.lbs.base.protocol.pager.PageResult;
import com.tarena.lbs.common.content.utils.SequenceGenerator;
import com.tarena.lbs.common.passport.principle.UserPrinciple;
import com.tarena.lbs.common.security.utils.LbsSecurityContenxt;
import com.tarena.lbs.marketing.api.MarketingApi;
import com.tarena.lbs.pojo.attach.param.PicUpdateParam;
import com.tarena.lbs.pojo.content.entity.ArticleSearchEntity;
import com.tarena.lbs.pojo.content.param.ArticleContentParam;
import com.tarena.lbs.pojo.content.query.ArticleQuery;
import com.tarena.lbs.pojo.content.vo.ArticleActionPageResultVO;
import com.tarena.lbs.pojo.content.vo.ArticleActionVO;
import com.tarena.lbs.pojo.content.vo.ArticleVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ArticleService {
    @Autowired
    private ArticleRepository articleRepository;
    @Autowired
    private ArticleESRepository articleESRepository;
    @DubboReference
    private AttachApi attachApi;
    @DubboReference
    private MarketingApi marketingApi;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    //ArticleQuery 里面存的是名数据 （页面所展示的数据）
    public PageResult<ArticleVO> articlePage(ArticleQuery query) throws BusinessException, IOException {
        //1.准备一个分页返回的对象
        PageResult<ArticleVO> voPage=new PageResult<>();
        //2.重新调整一个进入仓储层的查询入参 根据不同业务逻辑 封装不同的参数
        ArticleQuery repositoryQuery=new ArticleQuery();
        //3.获取查询文章分页请求来源 source=1 手机端 source=2 后台
        Integer source = query.getSource();
        //断言 source不能是空
        Asserts.isTrue(source==null,new BusinessException("-2","非法的查询来源"));
        if (source==1){
            //手机端查询
            //一个是地理位置 geoDistance
            repositoryQuery.setLongitude(query.getLongitude());
            repositoryQuery.setLatitude(query.getLatitude());
            //一个是首选标签 termQuery
            repositoryQuery.setArticleLabel(query.getArticleLabel());
            //一个上线文章 没上线的查不到 termQuery
            repositoryQuery.setArticleStatus(CollectionUtils.list(1));

        }else{
            //后台查询.哪些参数非空 当前查询用户是谁
            BeanUtils.copyProperties(query,repositoryQuery);
            //补充一个查询条件userId 当前登录商家账号id
            UserPrinciple userPrinciple = LbsSecurityContenxt.getLoginToken();
            Asserts.isTrue(userPrinciple==null,new BusinessException("-2","用户认证解析失败"));
            repositoryQuery.setUserId(userPrinciple.getId());
        }
        //4.调用仓储层查询 本来应该让仓储层返回一个分页的对象 转化vo处理 这里直接返回列表 查询所有 TODO
        List<ArticleSearchEntity> entities=articleRepository.searchArtitles(repositoryQuery);
        //5. 封装分页数据返回 分页数据 假的 写死的
        voPage.setPageSize(10);
        voPage.setPageNo(1);
        voPage.setTotal(100L);
        List<ArticleVO> vos=null;
        if (CollectionUtils.isNotEmpty(entities)){
            vos=entities.stream().map(entity->{
                ArticleVO vo=new ArticleVO();
                BeanUtils.copyProperties(entity,vo);
                return vo;
            }).collect(Collectors.toList());
        }
        voPage.setObjects(vos);
        return voPage;
    }


    public void articleAdd(ArticleContentParam param) throws BusinessException {
        //1.根据提交的param 封装ArticleSearchEntity
        ArticleSearchEntity  entity = generateArticleEntity(param);
        //2.调用ArticleESRepository.save(entity)
        articleESRepository.save(entity);
        //3.利用文章的id 和封面500 内容600远程调用attachApi(dubbo)
        bindArticlePic(param,entity.getId());
    }

    private void bindArticlePic(ArticleContentParam param, Integer bizId) {
        //封装 一批图片的绑定list 每个元素 都表示某个图片 绑定给某个业务的某个业务id
        //文章封面500 文章内容图片600
        List<PicUpdateParam> pics=new ArrayList<>();
        //1.封面是一张图片 内容是多张图片
        PicUpdateParam coverParam=new PicUpdateParam();
        coverParam.setBusinessType(500);
        coverParam.setBusinessId(bizId);
        String coverFileUuid=getFileUuidFromUrl(param.getArticleCover());
        coverParam.setFileUuid(coverFileUuid);
        pics.add(coverParam);
        //2.内容图片是一个数组 参数使用逗号分割 http://image1,http://iamge2
        String mainPicsStr = param.getArticleMainPic();
        String[] mainUrls = mainPicsStr.split(",");
        for (String mainUrl : mainUrls) {
            PicUpdateParam mainParam=new PicUpdateParam();
            mainParam.setBusinessType(600);
            mainParam.setBusinessId(bizId);
            String mainFileUuid = getFileUuidFromUrl(mainUrl);
            mainParam.setFileUuid(mainFileUuid);
            pics.add(mainParam);
        }
        attachApi.batchUpdateBusiness(pics);
    }

    private String getFileUuidFromUrl(String url) {
        String[] urlSplit = url.split("/");
        return urlSplit[urlSplit.length-1];
    }

    private ArticleSearchEntity generateArticleEntity(ArticleContentParam param) throws BusinessException {
        ArticleSearchEntity entity=new ArticleSearchEntity();
        //1.从param拷贝
        BeanUtils.copyProperties(param,entity);
        //2.entity缺少 作者的userId
        UserPrinciple userPrinciple = LbsSecurityContenxt.getLoginToken();
        Asserts.isTrue(userPrinciple==null,new BusinessException("-2","用户认证解析失败"));
        entity.setUserId(userPrinciple.getId());
        entity.setArticleUsername(userPrinciple.getNickname());
        //3.缺少docId 要求是整数(趋势递增的序列化生成器功能,可以使用雪花算法 美团leaf)
        entity.setId(SequenceGenerator.sequence());
        //4.时间数据 createTime updateTime
        entity.setCreateTime(new Date());
        entity.setUpdateTime(entity.getCreateTime());
        //5.location地理位置 前端latitude longitude 合并成一个字符串
        entity.setLocation(param.getLatitude()+","+param.getLongitude());
        //6.状态 status=0草稿 1上线 等于1才能被小程序用户查询到
        entity.setArticleStatus(1);
        //7.用户行为的埋点数据 和用户打标规则,数据统计,日志分析有关
        //7.1 文章被评论了几次 0
        entity.setAccessCount(0);
        //7.2 文章被点赞了几次 0
        entity.setLikeCount(0);
        //7.3 文章被收藏了几次 0
        entity.setFavoriteCount(0);
        log.info("创建文章的入参:{},封装entity:{}",param,entity);
        return entity;

    }
    //DAY11-PM 01 、查询手机定位范围内的文章标签集合
    public HashSet<String> getArticleLabels(ArticleQuery query) throws IOException {
        //1组织一个es的查询搜索 更具入参地址中心整理数据 esRepository不行了
        List<ArticleSearchEntity> entities=articleRepository.getArticleLabelsOnLocation(query);
        //2从entity中 提取标签集合
        HashSet<String> labels=new HashSet<>();
        if (CollectionUtils.isNotEmpty(entities)){
            for (ArticleSearchEntity entity : entities) {
                labels.add(entity.getArticleLabel());
            }
        }
        return labels;
    }

    public ArticleVO getArticle(Integer id) throws BusinessException {
        //1、先查文章
        Optional<ArticleSearchEntity> docAll = articleESRepository.findById(id);//拿到的是响应数据
        //我们需要的响应数据中 sourcejson可以转化的entity对象
        ArticleSearchEntity entity = docAll.get();
        //1.1 有一部分 点赞 收藏 评论次数 在redis
        HashOperations<String,String,String> opsForHash = stringRedisTemplate.opsForHash();
        String articleKey="articke:action:key:"+id;
        entity.setLikeCount(opsForHash.get(articleKey, 1+"")==null?0:Integer.parseInt(opsForHash.get(articleKey, 1+"")));
        entity.setFavoriteCount(opsForHash.get(articleKey, 2+"")==null?0:Integer.parseInt(opsForHash.get(articleKey, 2+"")));
        entity.setAccessCount(opsForHash.get(articleKey, 3+"")==null?0:Integer.parseInt(opsForHash.get(articleKey, 3+"")));

        //2、拿出文章的活动id
        Integer activityId = entity.getActivityId();
        if (activityId!=null){
            log.info("文章携带了活动：{}",activityId);
            //3、解析认证
            UserPrinciple userPrinciple = LbsSecurityContenxt.getLoginToken();
            Asserts.isTrue(userPrinciple==null,new BusinessException("-2","用户认证解析失败"));
            //设计约定的接口 从marketing服务获取结果 带还是不带这个用户活动id
            boolean activityVisible = false;
            activityVisible= marketingApi.activityVisible(userPrinciple.getId(), activityId);
            if (!activityVisible){
                 //是目标人群 携带activityId
                log.info("当前用户:{},不符合目标人群",userPrinciple.getId());
                entity.setActivityId(null);
             }
            //4、把活动信息设置到vo中

        }
        if (entity!=null){
            ArticleVO vo=new ArticleVO();
            BeanUtils.copyProperties(entity,vo);
            return vo;
        }else{
            return null;
        }
    }


    public ArticleActionPageResultVO actionList(ArticleQuery query) throws BusinessException, ExecutionException, InterruptedException {
        //1.解析jwt 拿到用户身份 拿到条件之一的actionUserId
        UserPrinciple userPrinciple = LbsSecurityContenxt.getLoginToken();
        Asserts.isTrue(userPrinciple==null,new BusinessException("-2","用户认证解析失败"));
        ArticleActionPageResultVO vo=new ArticleActionPageResultVO();
        //简单的执行时间统计
        Long startTime=new Date().getTime();
        //2.先查询action=1 actionUserId= and action=1|2|3
        //2.1 使用spring框架底层线程池 将并发的方法包装到异步线程 等待所有人完成获取结果 异步查询点赞
        CompletableFuture<PageResult<ArticleActionVO>> likeFuture =
                CompletableFuture.supplyAsync(() -> {
                    try {
                        return selectByAction(query, 1, userPrinciple.getId());
                    } catch (BusinessException e) {
                        throw new RuntimeException(e);
                    }
                });
        //2.2异步查询 收藏
        CompletableFuture<PageResult<ArticleActionVO>> favorFuture= CompletableFuture.supplyAsync(()-> {
            try {
                return selectByAction(query,2,userPrinciple.getId());
            } catch (BusinessException e) {
                throw new RuntimeException(e);
            }
        });
        //2.3异步查询 评论
        CompletableFuture<PageResult<ArticleActionVO>> commentFuture=CompletableFuture.supplyAsync(()-> {
            try {
                return selectByAction(query,3,userPrinciple.getId());
            } catch (BusinessException e) {
                throw new RuntimeException(e);
            }
        });
        //返回值 等待三个likeFuture commentFuture favorFuture全部拿到返回值
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(likeFuture, commentFuture, favorFuture);
       /* PageResult<ArticleActionVO> likeVos=selectByAction(query,1,userPrinciple.getId());
        //3.再查询action=2
        PageResult<ArticleActionVO> favorVos=selectByAction(query,2,userPrinciple.getId());
        //4.再查询action=3
        PageResult<ArticleActionVO> commentVos=selectByAction(query,3,userPrinciple.getId());
        */
        Long endTime=new Date().getTime();
        //5.封装结果返回
        vo.setCollectFutureOPageResult(favorFuture.get());//收藏数据
        vo.setLikeFuturePageResult(likeFuture.get());
        vo.setCommentFuturePageResult(commentFuture.get());
        log.info("当前用户行为文章查询耗时:"+(endTime-startTime)+"MS");
        return vo;
    }

    private PageResult<ArticleActionVO> selectByAction(ArticleQuery query, Integer action, Integer actionUserId) throws BusinessException {
        //1.准备一个返回值,节省分页数据查询 直接获取最终所有结果
        PageResult<ArticleActionVO> voPage=new PageResult<>();
        voPage.setPageSize(query.getPageSize());
        voPage.setPageNo(query.getPageNo());
        voPage.setTotal(100l);
        //2.封装分页数据
        List<ArticleSearchEntity> articles=articleRepository.getArticles(action,actionUserId);
        List<ArticleActionVO> vos=null;
        if (CollectionUtils.isNotEmpty(articles)){
            vos=articles.stream().map(entity->{
                //里面的数据 有一些来自lbs_action 主要来自于lbs_article
                ArticleActionVO vo=new ArticleActionVO();
                vo.setArticleId(entity.getId());
                vo.setLikePerson(actionUserId+"");
                vo.setArticleTitle(entity.getArticleTitle());
                vo.setArticleCover(entity.getArticleCover());
                //entity.getCreateTime().
                return vo;
            }).collect(Collectors.toList());
        }
        voPage.setObjects(vos);
        return voPage;
    }
}

